﻿#region Disclaimer / License

// Copyright (C) 2010, Jackie Ng
// https://github.com/jumpinjackie/mapguide-maestro
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

#endregion Disclaimer / License

using OSGeo.MapGuide.ObjectModels;
using OSGeo.MapGuide.ObjectModels.Common;
using System;
using System.Globalization;

namespace OSGeo.MapGuide.MaestroAPI.Schema
{
    /// <summary>
    /// Derives from <see cref="T:OSGeo.MapGuide.MaestroAPI.Schema.PropertyDefinition"/> and represents simple values or
    /// collections of simple values. This can take on any of the data types listed in the
    /// <see cref="T:OSGeo.MapGuide.MaestroAPI.Schema.DataPropertyType"/> enumeration.
    /// </summary>
    public class DataPropertyDefinition : PropertyDefinition
    {
        private DataPropertyDefinition()
        {
            this.DataType = DataPropertyType.String;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataPropertyDefinition"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        public DataPropertyDefinition(string name, string description)
            : this()
        {
            this.Name = name;
            this.Description = description;
        }

        /// <summary>
        /// Gets the data type of this property
        /// </summary>
        public DataPropertyType DataType { get; set; }

        /// <summary>
        /// Gets or sets the default value. Applies only to string data types
        /// </summary>
        public string DefaultValue { get; set; }

        /// <summary>
        /// Gets or sets the length of this property. Applies only to string and blob/clob data types
        /// </summary>
        public int Length { get; set; }

        /// <summary>
        /// Gets or sets whether this property accepts null values
        /// </summary>
        public bool IsNullable { get; set; }

        /// <summary>
        /// Gets or sets the precision of this property. Applies only to decimal data types
        /// </summary>
        public int Precision { get; set; }

        /// <summary>
        /// Gets or sets whether this property is read-only
        /// </summary>
        public bool IsReadOnly { get; set; }

        /// <summary>
        /// Gets or sets the scale of this property. Applies only to decimal data types.
        /// </summary>
        public int Scale { get; set; }

        /// <summary>
        /// Gets or sets whether this property automatically generates a value on insert.
        /// </summary>
        public bool IsAutoGenerated { get; set; }

        /// <summary>
        /// Gets the type of Property Definition
        /// </summary>
        public override PropertyDefinitionType Type => PropertyDefinitionType.Data;

        private static string GetXmlType(DataPropertyType dataPropertyType)
        {
            switch (dataPropertyType)
            {
                case DataPropertyType.Blob:
                    return "xs:hexBinary"; //NOXLATE
                case DataPropertyType.Boolean:
                    return "xs:boolean"; //NOXLATE
                case DataPropertyType.Byte:
                    return "xs:unsignedByte"; //NOXLATE
                case DataPropertyType.DateTime:
                    return "xs:dateTime"; //NOXLATE
                case DataPropertyType.Double:
                    return "xs:double"; //NOXLATE
                case DataPropertyType.Int16:
                    return "xs:short"; //NOXLATE
                case DataPropertyType.Int32:
                    return "xs:int"; //NOXLATE
                case DataPropertyType.Int64:
                    return "xs:long"; //NOXLATE
                case DataPropertyType.Single:
                    return "xs:float"; //NOXLATE
                case DataPropertyType.String:
                    return "xs:string"; //NOXLATE
                case DataPropertyType.Clob:
                    return "fdo:clob"; //NOXLATE
                default:
                    throw new ArgumentException();
            }
        }

        /// <summary>
        /// Gets the type of the data.
        /// </summary>
        /// <param name="xmlType">Type of the XML.</param>
        /// <returns></returns>
        public static DataPropertyType GetDataType(string xmlType)
        {
            switch (xmlType.ToLower())
            {
                case "xs:hexbinary": //NOXLATE
                case "xs:base64binary": //NOXLATE
                    return DataPropertyType.Blob;

                case "xs:boolean": //NOXLATE
                    return DataPropertyType.Boolean;

                case "fdo:byte": //NOXLATE
                case "xs:byte": //NOXLATE
                case "xs:unsignedbyte": //NOXLATE
                    return DataPropertyType.Byte;

                case "xs:date": //NOXLATE
                case "xs:datetime": //NOXLATE
                    return DataPropertyType.DateTime;

                case "fdo:double": //NOXLATE
                case "fdo:decimal": //NOXLATE
                case "xs:decimal": //NOXLATE
                case "xs:double": //NOXLATE
                    return DataPropertyType.Double;

                case "fdo:int16": //NOXLATE
                case "xs:int16": //NOXLATE
                case "xs:short": //NOXLATE
                case "xs:unsignedshort": //NOXLATE
                    return DataPropertyType.Int16;

                case "fdo:int32": //NOXLATE
                case "xs:int32": //NOXLATE
                case "xs:integer": //NOXLATE
                case "xs:negativeinteger": //NOXLATE
                case "xs:nonnegativeinteger": //NOXLATE
                case "xs:nonpositiveinteger": //NOXLATE
                case "xs:positiveinteger": //NOXLATE
                case "xs:unsignedint": //NOXLATE
                case "xs:int": //NOXLATE
                    return DataPropertyType.Int32;

                case "fdo:int64": //NOXLATE
                case "xs:int64": //NOXLATE
                case "xs:long": //NOXLATE
                case "xs:unsignedlong": //NOXLATE
                    return DataPropertyType.Int64;

                case "xs:float": //NOXLATE
                case "xs:single": //NOXLATE
                case "fdo:single": //NOXLATE
                    return DataPropertyType.Single;

                case "xs:string": //NOXLATE
                    return DataPropertyType.String;

                case "fdo:clob": //NOXLATE
                    return DataPropertyType.Clob;

                default:
                    throw new ArgumentException();
            }
        }

        /// <summary>
        /// Writes the current element's content
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="currentNode"></param>
        protected override void WriteXml(System.Xml.XmlDocument doc, System.Xml.XmlNode currentNode)
        {
            var en = Utility.EncodeFDOName(this.Name);

            var prop = doc.CreateElement("xs", "element", XmlNamespaces.XS); //NOXLATE
            prop.SetAttribute("name", en); //NOXLATE
            prop.SetAttribute("minOccurs", this.IsNullable ? "0" : "1"); //NOXLATE
            if (this.IsReadOnly)
                prop.SetAttribute("readOnly", XmlNamespaces.FDO, this.IsReadOnly.ToString().ToLower()); //NOXLATE
            if (this.IsAutoGenerated)
                prop.SetAttribute("autogenerated", XmlNamespaces.FDO, this.IsAutoGenerated.ToString().ToLower()); //NOXLATE

            //Write description node
            var anno = doc.CreateElement("xs", "annotation", XmlNamespaces.XS); //NOXLATE
            var docN = doc.CreateElement("xs", "documentation", XmlNamespaces.XS); //NOXLATE
            docN.InnerText = this.Description;
            prop.AppendChild(anno);
            anno.AppendChild(docN);

            var simp = doc.CreateElement("xs", "simpleType", XmlNamespaces.XS); //NOXLATE
            prop.AppendChild(simp);

            var rest = doc.CreateElement("xs", "restriction", XmlNamespaces.XS); //NOXLATE
            simp.AppendChild(rest);

            rest.SetAttribute("base", GetXmlType(this.DataType)); //NOXLATE
            if (this.DataType == DataPropertyType.String)
            {
                var max = doc.CreateElement("xs", "maxLength", XmlNamespaces.XS); //NOXLATE
                max.SetAttribute("value", this.Length.ToString(CultureInfo.InvariantCulture)); //NOXLATE

                rest.AppendChild(max);
            }

            currentNode.AppendChild(prop);
        }

        /// <summary>
        /// Set the current element's content from the current XML node
        /// </summary>
        /// <param name="node"></param>
        /// <param name="mgr"></param>
        protected override void ReadXml(System.Xml.XmlNode node, System.Xml.XmlNamespaceManager mgr)
        {
            var ro = Utility.GetFdoAttribute(node, "readOnly"); //NOXLATE
            var autogen = Utility.GetFdoAttribute(node, "autogenerated"); //NOXLATE

            this.DataType = GetDataType(node["xs:simpleType"]["xs:restriction"].Attributes["base"].Value.ToLower()); //NOXLATE
            this.IsNullable = (node.Attributes["minOccurs"] != null && node.Attributes["minOccurs"].Value == "0"); //NOXLATE
            this.IsReadOnly = (ro != null && ro.Value == "true"); //NOXLATE
            this.IsAutoGenerated = (autogen != null && autogen.Value == "true"); //NOXLATE
            this.DefaultValue = (node.Attributes["default"] != null ? node.Attributes["default"].Value : string.Empty); //NOXLATE
        }

        /// <summary>
        /// Convenience method to get whether this data property is numeric
        /// </summary>
        /// <returns></returns>
        public bool IsNumericType()
        {
            return this.DataType == DataPropertyType.Byte ||
                   this.DataType == DataPropertyType.Double ||
                   this.DataType == DataPropertyType.Int16 ||
                   this.DataType == DataPropertyType.Int32 ||
                   this.DataType == DataPropertyType.Int64 ||
                   this.DataType == DataPropertyType.Single;
        }

        private static ExpressionDataType GetExpressionType(DataPropertyType dt)
        {
            switch (dt)
            {
                case DataPropertyType.Blob:
                    return ExpressionDataType.Data_Blob;

                case DataPropertyType.Boolean:
                    return ExpressionDataType.Data_Boolean;

                case DataPropertyType.Byte:
                    return ExpressionDataType.Data_Byte;

                case DataPropertyType.Clob:
                    return ExpressionDataType.Data_Clob;

                case DataPropertyType.DateTime:
                    return ExpressionDataType.Data_DateTime;

                case DataPropertyType.Double:
                    return ExpressionDataType.Data_Double;

                case DataPropertyType.Int16:
                    return ExpressionDataType.Data_Int16;

                case DataPropertyType.Int32:
                    return ExpressionDataType.Data_Int32;

                case DataPropertyType.Int64:
                    return ExpressionDataType.Data_Int64;

                case DataPropertyType.Single:
                    return ExpressionDataType.Data_Single;

                case DataPropertyType.String:
                    return ExpressionDataType.Data_String;
            }
            throw new ArgumentException();
        }

        /// <summary>
        /// Gets the expression data type
        /// </summary>
        public override ExpressionDataType ExpressionType => GetExpressionType(this.DataType);
    }
}